const WebSocket = require("ws");
const crypto = require("crypto");

// 构造星火 WebSocket 鉴权 URL
function buildSignedUrl() {
  const host = process.env.SPARK_HOST || "spark-api.xf-yun.com";
  const path = process.env.SPARK_PATH || "/v1/x1"; // X1 默认路径，可通过环境变量覆盖
  const apiKey = process.env.SPARK_API_KEY;
  const apiSecret = process.env.SPARK_API_SECRET;

  if (!apiKey || !apiSecret || !process.env.SPARK_APP_ID) {
    console.error("[SPARK] Missing credentials", {
      APP: !!process.env.SPARK_APP_ID,
      KEY: !!apiKey,
      SEC: !!apiSecret,
    });
    throw new Error("Missing SPARK credentials in environment variables");
  }

  const date = new Date().toUTCString();
  const signString =
    `host: ${host}\n` + `date: ${date}\n` + `GET ${path} HTTP/1.1`;
  const signature = crypto
    .createHmac("sha256", apiSecret)
    .update(signString)
    .digest("base64");

  const authorizationOrigin = `api_key=\"${apiKey}\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"${signature}\"`;
  const authorization = Buffer.from(authorizationOrigin).toString("base64");

  const url = `wss://${host}${path}?authorization=${encodeURIComponent(
    authorization
  )}&date=${encodeURIComponent(date)}&host=${encodeURIComponent(host)}`;
  console.log("[SPARK] Upstream URL ready", { host, path, date });
  return url;
}

// 规范化前端消息 -> 星火请求体
function buildSparkRequest(payload) {
  // 后端强制固定
  const appId = process.env.SPARK_APP_ID || "821ea1c6";
  const domain = process.env.SPARK_DOMAIN || "x1";

  // 参数固定（可通过 .env 覆盖），不再读取客户端传入
  const temperature = Number(process.env.SPARK_TEMPERATURE ?? 0);
  // 默认给一个更保守的输出上限，避免配额或模型限制导致请求被拒
  const maxTokens = Number(process.env.SPARK_MAX_TOKENS ?? 1024);
  const presencePenalty = Number(process.env.SPARK_PRESENCE_PENALTY ?? 1);
  const frequencyPenalty = Number(process.env.SPARK_FREQUENCY_PENALTY ?? 0.02);
  const topK = Number(process.env.SPARK_TOP_K ?? 5);
  // 默认关闭 web_search，某些域或账号未开通该能力会报错；需要时可在 .env 显式开启
  const webSearchEnable =
    String(process.env.SPARK_WEB_SEARCH_ENABLE ?? "false") === "true";
  const webSearchMode = process.env.SPARK_WEB_SEARCH_MODE || "normal";

  const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];

  // 1) 仅保留允许的角色
  let messages = rawMessages
    .filter((m) => m && typeof m.content === "string" && m.content.trim().length > 0)
    .filter((m) => ["user", "assistant"].includes(m.role));

  // 2) 去掉前置的 assistant 消息，确保以用户消息开头
  while (messages.length && messages[0].role !== "user") {
    messages.shift();
  }

  // 3) 去掉末尾的 assistant 空消息（例如为流式占位而添加的空串）
  while (
    messages.length &&
    messages[messages.length - 1].role === "assistant" &&
    (!messages[messages.length - 1].content || messages[messages.length - 1].content.trim() === "")
  ) {
    messages.pop();
  }

  // 4) 若仍不存在用户消息，则返回 null 让上层处理
  const hasUser = messages.some((m) => m.role === "user");
  if (!hasUser) {
    return null;
  }

  // 转换为 spark 的 payload 格式
  return {
    header: {
      app_id: appId,
      uid: payload?.uid || "web-user",
    },
    parameter: {
      chat: {
        domain,
        temperature,
        max_tokens: maxTokens,
        presence_penalty: presencePenalty,
        frequency_penalty: frequencyPenalty,
        top_k: topK,
        ...(webSearchEnable
          ? {
              tools: [
                {
                  type: "web_search",
                  web_search: { enable: true, search_mode: webSearchMode },
                },
              ],
            }
          : {}),
      },
    },
    payload: {
      message: {
        text: messages.map((m) => ({ role: m.role, content: m.content })),
      },
    },
  };
}

// 将上游的增量内容提取出来并回传前端
function extractDelta(ans) {
  try {
    const data = JSON.parse(ans);
    const code = data?.header?.code;
    if (code !== 0) {
      return { error: data?.header?.message || `spark error code ${code}` };
    }
    const content = data?.payload?.choices?.text?.[0]?.content || "";
    const status = data?.payload?.choices?.status; // 2 表示结束
    return { content, status };
  } catch (e) {
    return { error: "Invalid upstream message" };
  }
}

function handleClientWS(client, req) {
  console.log("[WS] client connected", { url: req.url, ip: req.socket?.remoteAddress });
  let upstream = null;
  let closed = false;

  function safeClose(code, reason) {
    if (!closed) {
      closed = true;
      try {
        client.close(code, reason);
      } catch {}
      try {
        upstream && upstream.close();
      } catch {}
      console.log("[WS] closed", { code, reason });
    }
  }

  client.on("message", (raw) => {
    // 只处理第一次收到的消息，用于发起上游会话
    if (upstream) return;

    let payload;
    try {
      payload = JSON.parse(raw.toString());
    } catch (e) {
      console.warn("[WS] invalid JSON payload", e?.message);
      client.send(
        JSON.stringify({ type: "error", message: "Invalid JSON payload" })
      );
      return safeClose(1008, "invalid payload");
    }

    let url;
    try {
      url = buildSignedUrl();
    } catch (e) {
      console.error("[SPARK] config error", e?.message);
      client.send(
        JSON.stringify({
          type: "error",
          message: e?.message || "SPARK config error",
        })
      );
      return safeClose(1011, "config error");
    }

    upstream = new WebSocket(url, { rejectUnauthorized: false });

    upstream.on("open", () => {
      console.log("[SPARK] upstream opened");
      const sparkReq = buildSparkRequest(payload);
      if (!sparkReq) {
        console.warn("[SPARK] invalid conversation history: must start with a user message");
        client.send(
          JSON.stringify({
            type: "error",
            message: "对话历史无效：需要以用户消息开头",
          })
        );
        return safeClose(1008, "invalid conversation history");
      }
      // 打印安全的调试摘要，避免泄露具体文本
      try {
        const texts = sparkReq?.payload?.message?.text || [];
        console.log("[SPARK] request summary", {
          app_id: sparkReq?.header?.app_id ? true : false,
          domain: sparkReq?.parameter?.chat?.domain,
          max_tokens: sparkReq?.parameter?.chat?.max_tokens,
          temperature: sparkReq?.parameter?.chat?.temperature,
          messages_count: texts.length,
          first3_roles: texts.slice(0, 3).map((t) => t.role),
        });
      } catch {}
      upstream.send(JSON.stringify(sparkReq));
      client.send(JSON.stringify({ type: "started" }));
    });

    upstream.on("message", (msg) => {
      const { content, status, error } = extractDelta(msg.toString());
      if (error) {
        console.warn("[SPARK] upstream error frame", error);
        client.send(JSON.stringify({ type: "error", message: error }));
        return;
      }
      if (content) {
        client.send(JSON.stringify({ type: "delta", content }));
      }
      if (status === 2) {
        client.send(JSON.stringify({ type: "end" }));
        safeClose(1000, "done");
      }
    });

    upstream.on("error", (err) => {
      console.error("[SPARK] upstream error", err?.message);
      client.send(
        JSON.stringify({
          type: "error",
          message: err.message || "upstream error",
        })
      );
      safeClose(1011, "upstream error");
    });

    upstream.on("close", (code, reason) => {
      console.log("[SPARK] upstream closed", { code, reason: String(reason || "") });
      safeClose(1000, "upstream closed");
    });
  });

  client.on("close", () => {
    safeClose(1000, "client closed");
  });

  client.on("error", () => {
    safeClose(1011, "client error");
  });
}

module.exports = { handleClientWS };
